package edu.kit.aifb.ldsbench.generator.opt.util2;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;

import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.impl.NumericLiteralImpl;
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.query.BindingSet;
import org.openrdf.query.BooleanQuery;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.TupleQuery;
import org.openrdf.query.TupleQueryResult;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;

import edu.kit.aifb.ldstream.env.Environment;
import edu.kit.aifb.ldstream.learner.out.xml.Tags;
import edu.kit.aifb.ldstream.learner.util.URIComparator;
import edu.kit.aifb.ldstream.stat.distrib.BinomialDistribution;
import edu.kit.aifb.ldstream.stat.distrib.ContinuousUniformDistribution;
import edu.kit.aifb.ldstream.stat.distrib.DiscreteUniformDistribution;
import edu.kit.aifb.ldstream.stat.distrib.ExponentialDistribution;
import edu.kit.aifb.ldstream.stat.distrib.IDistribution;
import edu.kit.aifb.ldstream.stat.distrib.NormalDistribution;
import edu.kit.aifb.ldstream.stat.distrib.PoissonDistribution;

public class SesameQuery {

	public static final int VALUE_DISTRIB = 0;
	public static final int PROPERTY_DISTRIB = 1;
	public static final int INSTANCE_DISTRIB = 2;

	private RepositoryConnection con;

	public SesameQuery(RepositoryConnection con) {
		this.con = con;
	}


	public Set<URI> getInstanceNames(URI type, final int from, final int to){
		if(from < to){
			throw new IllegalArgumentException("Must be a descending range for getInstanceNames");
		}
		Set<URI> set = new TreeSet<URI>(new URIComparator());
		RepositoryResult<Statement> result;
		Statement s;
		int count = 0;
		int pos = from;
		while(pos - to != -1){
			count++;
			try {
				result = this.con.getStatements(type, new URIImpl(Environment.INSTANCE_NAME + pos), null, false, type);
				if(result.hasNext()){
					s = result.next();
					set.add(new URIImpl(s.getObject().stringValue()));
					this.con.remove(type, new URIImpl(Environment.INSTANCE_NAME + pos), null, type);
					pos--;
				}
			} catch (RepositoryException e) {
				e.printStackTrace();
			}
		}
		//update the num instance names
		try {
			this.con.remove(type, new URIImpl(Environment.NUM_INSTANCE_NAME), null, type);
			this.con.add(type, new URIImpl(Environment.NUM_INSTANCE_NAME), new NumericLiteralImpl(from - count + 1), type);
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		
		return set;

	}	
	
	public int getNumInstanceNames(URI type){
		int count = 0;
		StringBuilder query = new StringBuilder("SELECT ?x FROM <");
		query.append(type.stringValue())
		.append("> WHERE{ <")
		.append(type.stringValue())
		.append("> <")
		.append(Environment.NUM_INSTANCE_NAME)
		.append("> ?x }");

		TupleQuery tupleQuery;
		try {
			tupleQuery = this.con.prepareTupleQuery(QueryLanguage.SPARQL, query.toString());
			TupleQueryResult result = tupleQuery.evaluate();
			if(result.hasNext()){
				count = Integer.parseInt(result.next().getValue("x").stringValue());
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		} catch (MalformedQueryException e) {
			e.printStackTrace();
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
		}
		return count;
	}

	public String getMapping(URI property, URI typeContext, int mappingNr){
		String maping = null;

		String query = "SELECT ?x FROM <" + typeContext.stringValue() + "> WHERE{ <" + property.stringValue() + "> " + Environment.NUM_MAPPINGS + " ?x }" ;
		int numMappings = 0;
		try {
			TupleQuery tupleQuery = this.con.prepareTupleQuery(QueryLanguage.SPARQL, query);
			TupleQueryResult result = tupleQuery.evaluate();
			try{
				if(result.hasNext()){
					numMappings = Integer.parseInt(result.next().getValue("x").stringValue());
				}
			} finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		} catch (MalformedQueryException e) {
			e.printStackTrace();
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
		}

		if(numMappings > 0){
			query = "SELECT ?x FROM <" + typeContext.stringValue() + "> WHERE{<" 
					+ property.stringValue() + "> " + Environment.MAPPING + (mappingNr % numMappings) + "> ?x }";
			try {
				TupleQuery tupleQuery = this.con.prepareTupleQuery(QueryLanguage.SPARQL, query);
				TupleQueryResult result = tupleQuery.evaluate();
				try{
					if(result.hasNext()){
						maping = result.next().getValue("x").stringValue();
					}
				} finally{
					result.close();
				}
			} catch (RepositoryException e) {
				e.printStackTrace();
			} catch (MalformedQueryException e) {
				e.printStackTrace();
			} catch (QueryEvaluationException e) {
				e.printStackTrace();
			}
		}

		return maping;
	}

	public IDistribution getDistribution(URI subject, URI context, final int distribType){
		IDistribution distrib = null;
		StringBuilder query = null;
		String predicate;

		if(distribType == SesameQuery.INSTANCE_DISTRIB || distribType == SesameQuery.PROPERTY_DISTRIB 
				|| distribType == SesameQuery.VALUE_DISTRIB){

			if(distribType == INSTANCE_DISTRIB){
				predicate = Environment.INSTANCE_OCC_URI;
			} else if(distribType == PROPERTY_DISTRIB){
				predicate = Environment.PROPERTY_OCC_DISTRIB;
			} else{
				predicate = Environment.VALUE_DISTRIB;
			}
			query = new StringBuilder("SELECT ?x FROM <").append(context.stringValue())
					.append("> WHERE{ <").append(subject.stringValue()).append("> ")
					.append(predicate).append(" ?x }");
		}

		String distribName = "";
		if(query != null){
			try {
				TupleQuery tupleQuery = this.con.prepareTupleQuery(QueryLanguage.SPARQL, query.toString());
				TupleQueryResult result = tupleQuery.evaluate();
				try{
					if(result.hasNext()){
						distribName = result.next().getValue("x").stringValue();
					}
				}finally{
					result.close();
				}
			} catch (RepositoryException e) {
				e.printStackTrace();
			} catch (MalformedQueryException e) {
				e.printStackTrace();
			} catch (QueryEvaluationException e) {
				e.printStackTrace();
			}

			double param1, param2;

			if(distribName.equals(Tags.NORMAL_DISTRIBUTION_TAG)){
				param1 = getParamValue(subject, Tags.SD, context, distribType);
				param2 = getParamValue(subject, Tags.MEAN, context, distribType);
				distrib = new NormalDistribution(param2, param1);
			} else if(distribName.equals(Tags.EXPONENTIAL_DISTRIBUTION_TAG)){
				param1 = getParamValue(subject, Tags.LAMBDA, context, distribType);
				distrib = new ExponentialDistribution(param1);
			} else if(distribName.equals(Tags.POISSON_DISRIBUTION_TAG)){
				param1 = getParamValue(subject, Tags.LAMBDA, context, distribType);
				distrib = new PoissonDistribution(param1);
			} else if(distribName.equals(Tags.BINOMIAL_DISTRIBUTION_TAG)){
				param1 = getParamValue(subject, Tags.P, context, distribType);
				param2 = getParamValue(subject, Tags.N, context, distribType);
				distrib = new BinomialDistribution(param1, (int) param2);
			} else if(distribName.equals(Tags.DISCRETE_UNIFORM_DISTRIBUTION_TAG)){
				param1 = getParamValue(subject, Tags.MIN, context, distribType);
				param2 = getParamValue(subject, Tags.MAX, context, distribType);
				distrib = new DiscreteUniformDistribution(param1, param2);
			} else if(distribName.equals(Tags.CONTINUOUS_UNIFORM_DISTRIBUTION)){
				param1 = getParamValue(subject, Tags.MAX, context, distribType);
				param2 = getParamValue(subject, Tags.MIN, context, distribType);
				distrib = new ContinuousUniformDistribution(param2, param1);
			}
		}
		return distrib;
	}

	private double getParamValue(URI subject, String paramName, URI context, final int distribType){
		double value = 0d; 
		StringBuilder query = null;
		String predicate;
		if(distribType == SesameQuery.INSTANCE_DISTRIB || distribType == SesameQuery.PROPERTY_DISTRIB 
				|| distribType == SesameQuery.VALUE_DISTRIB){
			if(distribType == INSTANCE_DISTRIB){
				predicate = Environment.INSTANCE_OCC_PARAM;
			}else if(distribType == PROPERTY_DISTRIB){
				predicate = Environment.PROPERTY_DISTRIB_PARAM;
			}else{
				predicate = Environment.VALUE_DISTRIB_PARAM;
			}
			query = new StringBuilder("SELECT ?x FROM <").append(context.stringValue())
					.append("> WHERE{<").append(subject.stringValue()).append("> ")
					.append(predicate).append(paramName).append("> ?x }");
		}
		if(query != null){
			try {
				TupleQuery tupleQuery = this.con.prepareTupleQuery(QueryLanguage.SPARQL, query.toString());
				TupleQueryResult result = tupleQuery.evaluate();
				try{
					if(result.hasNext()){
						value = Double.parseDouble(result.next().getValue("x").stringValue());
					}
				} finally{
					result.close();
				}
			} catch (RepositoryException e) {
				e.printStackTrace();
			} catch (MalformedQueryException e) {
				e.printStackTrace();
			} catch (QueryEvaluationException e) {
				e.printStackTrace();
			}
		}
		return value;

	}

	public boolean hasMapping(URI property, URI typeContext){
		boolean hasMapping = false;

		String query = "ASK FROM <" + typeContext.stringValue() + "> {<" + property.stringValue() + "> " + Environment.NUM_MAPPINGS + " ?x}";

		try {
			BooleanQuery booleanQuery = this.con.prepareBooleanQuery(QueryLanguage.SPARQL, query);
			hasMapping  = booleanQuery.evaluate();
		} catch (RepositoryException e) {
			e.printStackTrace();
		} catch (MalformedQueryException e) {
			e.printStackTrace();
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
		}

		return hasMapping;
	}

	public Set<URI> getTypes(){
		Set<URI> typeList = new TreeSet<URI>(new URIComparator()); 

		String query = "SELECT DISTINCT ?type FROM " + Environment.TYPE_HAS_PROPERTY_CONTEXT + " WHERE{ ?type " + Environment.TYPE_HAS_PROPERTY + " ?y}" ; 

		try {
			TupleQuery tupleQuery = this.con.prepareTupleQuery(QueryLanguage.SPARQL, query);
			TupleQueryResult result = tupleQuery.evaluate();
			try{
				BindingSet set;
				while(result.hasNext()){
					set = result.next();
					typeList.add(new URIImpl(set.getValue("type").stringValue()));
				}
			} finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		} catch (MalformedQueryException e) {
			e.printStackTrace();
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
		}

		return typeList;
	}

	public int numProperties(URI type){
		int numProps = 0;

		String query = "SELECT (COUNT(?x) AS ?count ) FROM " + Environment.TYPE_HAS_PROPERTY_CONTEXT 
				+ " WHERE{ <" + type.stringValue() + "> " + Environment.TYPE_HAS_PROPERTY + " ?x }" ;

		try {
			TupleQuery tupleQuery = this.con.prepareTupleQuery(QueryLanguage.SPARQL, query);
			TupleQueryResult result = tupleQuery.evaluate();
			if(result.hasNext()){
				numProps = Integer.parseInt(result.next().getValue("count").stringValue());
			}
			result.close();
		} catch (RepositoryException e) {
			e.printStackTrace();
		} catch (MalformedQueryException e) {
			e.printStackTrace();
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
		}

		return numProps;
	}

	public boolean isInterDomain(URI property, URI typeContext){
		boolean itd = false;
		String query = "ASK FROM <" + typeContext.stringValue() + "> {<" + property.stringValue() + "> " + Environment.PROPERTY_IS_INTER_DOMAIN + " \"true\" }";

		try {
			BooleanQuery booleanQuery = this.con.prepareBooleanQuery(QueryLanguage.SPARQL, query);
			itd = booleanQuery.evaluate();
		} catch (RepositoryException e) {
			e.printStackTrace();
		} catch (MalformedQueryException e) {
			e.printStackTrace();
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
		}
		return itd;
	}

	public boolean isBNodeProperty(URI property, URI typeContext){
		boolean isBNode = false;
		String query = "ASK FROM <" + typeContext.stringValue() + "> {<" + property.stringValue() + "> " + Environment.PROPERTY_IS_BNODE + " \"true\" }";

		try {
			BooleanQuery booleanQuery = this.con.prepareBooleanQuery(QueryLanguage.SPARQL, query);
			isBNode = booleanQuery.evaluate();
		} catch (RepositoryException e) {
			e.printStackTrace();
		} catch (MalformedQueryException e) {
			e.printStackTrace();
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
		}

		return isBNode;


	}

	public boolean typeHasProperty(URI type, URI property){
		boolean hasProp = false;
		String query = "ASK FROM " + Environment.TYPE_HAS_PROPERTY_CONTEXT + " {<" + type.stringValue() + "> " 
				+ Environment.TYPE_HAS_PROPERTY + " <" + property.stringValue() + ">}";

		try {
			BooleanQuery booleanQuery = this.con.prepareBooleanQuery(QueryLanguage.SPARQL, query);
			hasProp = booleanQuery.evaluate();
		} catch (RepositoryException e) {
			e.printStackTrace();
		} catch (MalformedQueryException e) {
			e.printStackTrace();
		} catch (QueryEvaluationException e) {
			e.printStackTrace();
		}


		return hasProp;
	}

	public Set<URI> getOutgoingProperties(URI type){

		RepositoryResult<Statement> result = null;
		Set<URI> typesHierarchy = getSuperTypes(type);
		typesHierarchy = getSubTypes(type, typesHierarchy);
		Set<URI> outgoingProbs = new HashSet<URI>(typesHierarchy.size());
		Statement s;

		/*
		 * Collecting all properties that have the given type set as domain.  
		 */
		for(URI currentType : typesHierarchy){
			try {
				result = this.con.getStatements(null, RDFS.DOMAIN , currentType, false, Environment.SCHEMA_URI);
				try{
					while(result.hasNext()){
						s = result.next();
						//	System.out.println(s.getSubject().stringValue());
						outgoingProbs.add((URI) s.getSubject());
					}
				}finally{
					result.close();
				}
			}catch(RepositoryException e){}
		}
		return outgoingProbs;
	}


	public ArrayList<URI> getRangeOfProperty(URI property){

		RepositoryResult<Statement> result = null;
		ArrayList<URI> range = null;

		/*
		 * Collecting all classes that the given property links to, 
		 * get the classes which are linked via the property rdf:range in the schema of the given property
		 */

		try {
			result = this.con.getStatements(property, RDFS.RANGE , null, false, Environment.SCHEMA_URI);
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		if(result != null){
			try{
				range = new ArrayList<URI>(3);
				try{
					Statement s;
					while(result.hasNext()){
						s = result.next();
						range.add((URI) s.getObject());
					}
				}finally{
					result.close();
				}
			}catch(RepositoryException e){}
		}
		// range normally links to 1 URI
		range.trimToSize();
		return range;
	}

	private Set<URI> getSuperTypes(URI subType){
		Set<URI> superTypes = new HashSet<URI>();
		superTypes.add(subType);
		return getSuperTypes(superTypes, subType);
	}

	private Set<URI> getSuperTypes(Set<URI> superTypes, URI currentURI){
		RepositoryResult<Statement> result = null;
		Statement s = null;
		URI nextType;

		// crawling for all super types by following the rdfs:subClassOf
		try {
			result = this.con.getStatements(currentURI, RDFS.SUBCLASSOF, null, false, Environment.SCHEMA_URI);
			try{
				while(result.hasNext()){
					s = result.next();
					nextType = (URI) s.getObject();
					// the super type could have already been crawled 
					if(!superTypes.contains(nextType)){
						superTypes.add(nextType);
						getSuperTypes(superTypes, nextType);
					}
				}
			}finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		return superTypes;
	}

	private Set<URI> getSubTypes(URI parent, Set<URI> set){
		RepositoryResult<Statement> result = null;
		Statement s = null;
		URI nextType;
		try{
			result = this.con.getStatements(null, RDFS.SUBCLASSOF, parent, false, Environment.SCHEMA_URI);
			try{
				while(result.hasNext()){
					s = result.next();
					nextType =(URI) s.getSubject();
					set.add(nextType);
					getSubTypes(nextType, set);
				}
			}finally{
				result.close();
			}
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
		return set;
	}

	public void close(){
		try {
			this.con.close();
		} catch (RepositoryException e) {
			e.printStackTrace();
		}
	}

}
